home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / PCGI_PUBLISHER.PY < prev    next >
Encoding:
Python Source  |  2000-09-07  |  14.0 KB  |  391 lines

  1. # PCGIPublisher.py
  2. # Persistent CGI Publisher - jeffbauer@bigfoot.com
  3. #
  4. # Copyright (c) 1998, Digital Creations, Fredericksburg, VA, USA.  All
  5. # rights reserved. This software includes contributions from Jeff Bauer.
  6. #
  7. # Redistribution and use in source and binary forms, with or without
  8. # modification, are permitted provided that the following conditions are
  9. # met:
  10. #   o Redistributions of source code must retain the above copyright
  11. #     notice, this list of conditions, and the disclaimer that follows.
  12. #
  13. #   o Redistributions in binary form must reproduce the above copyright
  14. #     notice, this list of conditions, and the following disclaimer in
  15. #     the documentation and/or other materials provided with the
  16. #     distribution.
  17. #
  18. #   o All advertising materials mentioning features or use of this
  19. #     software must display the following acknowledgement:
  20. #
  21. #       This product includes software developed by Digital Creations
  22. #       and its contributors.
  23. #
  24. #   o Neither the name of Digital Creations nor the names of its
  25. #     contributors may be used to endorse or promote products derived
  26. #     from this software without specific prior written permission.
  27. #
  28. #
  29. # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS *AS IS* AND
  30. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  31. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  32. # PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS
  33. # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  34. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  35. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  36. # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  37. # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  38. # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
  39. # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  40. #
  41. # 2.0 alpha4:
  42. #   - adds NT support, using INET sockets
  43. #   - added Mike Fletcher's Microsoft IIS hack, suggested by Amos Latteier
  44. #
  45. # 2.0 alpha3:
  46. #   - adds error checking
  47. #   - expands header to 10 bytes (extra byte defines an out-of-band msg)
  48. #
  49. # 2.0 alpha1:
  50. #   - add support for new PCGI_ directives
  51. #   - re-write module as a class, rather than a set of functions
  52.  
  53. __version__ = "2.0a4"
  54.  
  55. import string, sys, os
  56.  
  57. class PCGIPublisher:
  58.     def __init__(self, resource=None):
  59.         ### resources passed from the environment, info file or pcgi-wrapper
  60.         self.errorLogFile = None
  61.         self.hostname = None
  62.         self.port = 0
  63.         self.insertPath = None
  64.         self.moduleName = None
  65.         self.modulePath = None
  66.         self.socketFile = None
  67.         self.pidFile = None
  68.         self.swhome = None
  69.         self.sock = None
  70.  
  71.         ### bound imports ###
  72.         self.StringIO = None
  73.         self.publish_module = None
  74.  
  75.         ### other ###
  76.         self.bufsize = 8192
  77.         self.error = 0
  78.  
  79.         if resource is None:
  80.             self.getResources()
  81.         else:
  82.             self.resource = resource
  83.         self.initPCGI()
  84.  
  85.     def cleanup(self):
  86.         if self.socketFile:
  87.             import os
  88.             if os.path.exists(self.socketFile): 
  89.                 try:
  90.                     os.unlink(self.socketFile)
  91.                 except os.error:
  92.                     pass
  93.  
  94.     def getResources(self):
  95.         """
  96.         Obtain the publisher resources from the environment.  Done here
  97.         in a separate method to make it easy to override, because we may
  98.         not always and forever obtain our resources from the environment.
  99.         """
  100.         import os
  101.         self.resource = os.environ
  102.  
  103.     def fatalError(self, errmsg=''):
  104.         if self.errorLogFile:
  105.             import sys, traceback, StringIO
  106.             try:
  107.                 from time import asctime, localtime, time
  108.                 timeStamp = asctime(localtime(time()))
  109.             except ImportError:
  110.                 timeStamp = '???'
  111.             try:
  112.                 f = open(self.errorLogFile, 'a+')
  113.                 f.write("%s  %s\n" % (timeStamp, errmsg))
  114.                 if sys.exc_type != SystemExit:
  115.                     trace=StringIO.StringIO()
  116.                     traceback.print_exception(sys.exc_type,
  117.                                               sys.exc_value,
  118.                                               sys.exc_traceback,
  119.                                               None,
  120.                                               trace)
  121.                     f.write("  %s\n" % trace.getvalue())
  122.                 f.close()
  123.             except IOError:
  124.                 pass
  125.         self.cleanup()
  126.         self.error = 1
  127.  
  128.     def initPCGI(self):
  129.         import os
  130.         self.initPrincipia()
  131.         if self.resource.has_key('PCGI_ERROR_LOG'):
  132.             self.errorLogFile = self.resource['PCGI_ERROR_LOG']
  133.         if self.resource.has_key('PCGI_HOST'):
  134.             self.hostname = self.resource['PCGI_HOST']
  135.         if self.resource.has_key('PCGI_INSERT_PATH'):
  136.             self.insertPath = self.resource['PCGI_INSERT_PATH']
  137.         if self.resource.has_key('PCGI_MODULE_PATH'):
  138.             self.modulePath = self.resource['PCGI_MODULE_PATH']
  139.         if self.resource.has_key('PCGI_NAME'):
  140.             self.moduleName = self.resource['PCGI_NAME']
  141.         if self.resource.has_key('PCGI_PID_FILE'):
  142.             self.pidFile = self.resource['PCGI_PID_FILE']
  143.         if self.resource.has_key('PCGI_PORT'):
  144.             import string
  145.             try: self.port = string.atoi(self.resource['PCGI_PORT'])
  146.             except ValueError: pass
  147.         if self.resource.has_key('PCGI_SOCKET_FILE'):
  148.             self.socketFile = self.resource['PCGI_SOCKET_FILE']
  149.         self.insertSysPath()
  150.  
  151.         if not self.moduleName:
  152.             return self.fatalError("missing module name, try specifying PCGI_NAME")
  153.  
  154.         ### TODO: probably should make an attempt to import self.moduleName
  155.         ### to provide the user with earliest possible response.
  156.  
  157.         try:
  158.             from cStringIO import StringIO
  159.         except:
  160.             from StringIO import StringIO
  161.         self.StringIO = StringIO
  162.  
  163.         try:
  164.             from ZPublisher import publish_module
  165.         except ImportError:
  166.             try:
  167.                 from cgi_module_publisher import publish_module
  168.             except ImportError:
  169.                 return self.fatalError(
  170.                     "unable to import publish_module from ZPublisher")
  171.  
  172.         self.publish_module = publish_module
  173.  
  174.         if not self.pidFile:
  175.             return self.fatalError("missing pid file")
  176.  
  177.         ### create pid file ###
  178.         try:
  179.             f = open(self.pidFile, 'wb')
  180.             f.write(str(os.getpid()))
  181.             f.close()
  182.         except IOError:
  183.             return self.fatalError("unable to write to pid file: %s" % self.pidFile)
  184.         import socket
  185.         if not self.socketFile:
  186.             return self.fatalError("missing socket file")
  187.         if self.port:
  188.             if os.sep == '/':
  189.                 return self.fatalError("INET sockets not yet available on Unix")
  190.             if self.hostname is None:
  191.                 self.hostname = socket.gethostname()
  192.             try:
  193.                 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  194.                 self.sock.bind((self.hostname, self.port))
  195.             except socket.error:
  196.                 return self.fatalError("error binding to socket: %s" % self.socketFile)
  197.             try:
  198.                 sf = open(self.socketFile, 'wb')
  199.                 sf.write("%s\n%s\n" % (self.hostname, self.port))
  200.                 sf.close()
  201.             except IOError:
  202.                 return self.fatalError("error attempting to write socket file: %s" % self.socketFile)
  203.         else:
  204.             try: os.unlink(self.socketFile)
  205.             except os.error: pass
  206.             try:
  207.                 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  208.                 self.sock.bind(self.socketFile)
  209.             except socket.error:
  210.                 return self.fatalError("error binding to socket: %s" % self.socketFile)
  211.             try: os.chmod(self.socketFile, 0777)
  212.             except os.error: pass
  213.  
  214.     def initPrincipia(self):
  215.         if self.resource.has_key('SOFTWARE_NAME'):
  216.             self.moduleName = self.resource['SOFTWARE_NAME']
  217.  
  218.     def insertSysPath(self, insertPath=None):
  219.         import os, sys, string
  220.         if insertPath is None: 
  221.             insertPath = self.insertPath
  222.         if insertPath:
  223.             sys.path[0:0]=string.split(insertPath,':')
  224.         elif self.resource.has_key('PCGI_WORKING_DIR'):
  225.             ### Note: PCGI_WORKING_DIR is a deprecated pcgi directive ###
  226.             workDir = self.resource['PCGI_WORKING_DIR']
  227.             while workDir[-1:]=='/' or workDir[-1:]=='\\':
  228.                 workDir=workDir[:-1]
  229.             sys.path[0:0]=[workDir]
  230.         else:
  231.             pass
  232.  
  233.         # The present assumption is that if the module path isn't in
  234.         # sys.path, we want it put there, even if the PCGI_INSERT_PATH 
  235.         # directive has been specified.
  236.         if self.modulePath:
  237.             d, s = os.path.split(self.modulePath)
  238.             if not d in sys.path:
  239.                 sys.path[0:0] = [d]
  240.             # If the moduleName is not known at this time, we make a
  241.             # reasonable guess based on the path.  It might behoove the
  242.             # user to explicitly specify PCGI_NAME, however.
  243.             if not self.moduleName:
  244.                 import string
  245.                 self.moduleName = string.splitfields(s,'.')[0]
  246.  
  247.     def handler(self, conn):
  248.         from string import split, join, atoi
  249.         hdr = conn.recv(10)
  250.  
  251.         size = atoi(hdr)
  252.         buff = []
  253.         while size > 0:
  254.             data = conn.recv(size)
  255.             size = size - len(data)
  256.             buff.append(data)
  257.  
  258.         ### XXX - Later: add out-of-band data handling ###
  259.         if (hdr[0] != '0'):
  260.             return
  261.  
  262.         env = {}
  263.         for i in filter(None, split(join(buff,''),'\000')):
  264.             e = split(i,'=')
  265.             if len(e) >= 2:
  266.                 env[e[0]] = join(e[1:],'=')
  267.             else:
  268.                 env[e[0]]=''
  269.         size = atoi(conn.recv(10))
  270.         if size > 1048576:
  271.             ### write large upload data to a file ###
  272.             from tempfile import TemporaryFile
  273.             stdin = TemporaryFile('w+b')
  274.             bufsize = self.bufsize
  275.             while size > 0:
  276.                 if size < bufsize: 
  277.                     bufsize=size
  278.                 data = conn.recv(bufsize)
  279.                 size = size - len(data)
  280.                 stdin.write(data)
  281.             stdin.seek(0,0)
  282.         else:
  283.             ### use StringIO for smaller data ###
  284.             buff = []
  285.             while size > 0:
  286.                 data = conn.recv(size)
  287.                 size = size-len(data)
  288.                 buff.append(data)
  289.             stdin = self.StringIO(join(buff,''))
  290.         stdout = self.StringIO()
  291.         stderr = self.StringIO()
  292.  
  293.         ### IIS hack to fix broken PATH_INFO
  294.         ### taken from Mike Fletcher's win_cgi_module_publisher
  295.         import string
  296.         if env.has_key('SERVER_SOFTWARE') and string.find(env['SERVER_SOFTWARE'],'Microsoft-IIS') != -1:
  297.             script = filter(None,string.split(string.strip(env['SCRIPT_NAME']),'/'))
  298.             path = filter(None,string.split(string.strip(env['PATH_INFO']),'/'))
  299.             env['PATH_INFO'] = string.join(path[len(script):],'/')
  300.  
  301.         try:
  302.             self.publish_module(self.moduleName,stdin=stdin,stdout=stdout,stderr=stderr,environ=env)
  303.         except:
  304.             self.fatalError("unable to publish module")
  305.  
  306.         stdin.close()
  307.         stdout=stdout.getvalue()
  308.         stderr=stderr.getvalue()
  309.  
  310.         stdout_len=len(stdout)
  311.         conn.send('%010d' % len(stdout))
  312.         to_send=stdout_len
  313.         if to_send > 0:
  314.             while 1:
  315.                 sent = conn.send(stdout)
  316.                 if sent == to_send:
  317.                     break
  318.                 else:
  319.                     to_send = to_send - sent
  320.                     stdout = stdout[sent:]
  321.                 
  322.         stderr_len=len(stderr)
  323.         conn.send('%010d' % stderr_len)
  324.         to_send=stderr_len
  325.         if to_send > 0:
  326.             while 1:
  327.                 sent = conn.send(stderr)
  328.                 if sent == to_send:
  329.                     break
  330.                 else:
  331.                     to_send = to_send - sent
  332.                     stderr = stderr[sent:]
  333.                     
  334.         conn.close()
  335.  
  336.     def listen(self):
  337.         """
  338.         Note to sub-classes:  Aside from the constructor, listen() should 
  339.         be the only method you *must* invoke.
  340.         """
  341.         if self.error:
  342.             return self.fatalError("attempt to listen after fatal error")
  343.  
  344.         import os, sys
  345.         if not self.sock:
  346.             return self.fatalError("no socket available")
  347.  
  348.         self.sock.listen(512)
  349.  
  350.         stdlog('stderr')
  351.         stdlog('stdout')
  352.  
  353.         while not self.error:
  354.             conn, accept = self.sock.accept()
  355.             try:
  356.                 self.handler(conn)
  357.             except socket.error:
  358.                 pass
  359.  
  360. def stdlog(name):
  361.     NAME=string.upper(name)+'_LOG'
  362.     if os.environ.has_key(NAME):
  363.         f=os.environ[NAME]
  364.     else:
  365.         f='/dev/null'
  366.     try: f=open(f,'a')
  367.     except: f=None
  368.     getattr(sys, name).close()
  369.     if f is not None: setattr(sys, name, f)
  370.  
  371. def main():
  372.     try:
  373.         import os, sys, string, traceback
  374.         pcgiPublisher = PCGIPublisher(os.environ)
  375.         if not pcgiPublisher.error:
  376.             pcgiPublisher.listen()
  377.     except ImportError:
  378.         print "Content-type: text/html"
  379.         print
  380.         print "PCGIPublisher catastrophic import error"
  381.  
  382. if __name__ == '__main__':
  383.     try: 
  384.         main()
  385.     finally:
  386.         import os
  387.         if os.environ.has_key('PCGI_SOCKET_FILE'):
  388.             try: os.unlink(os.environ['PCGI_SOCKET_FILE'])
  389.             except os.error: pass
  390.